package com.sync.data.common.interceptor;

import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import com.sync.data.common.annotation.EncryptField;
import com.sync.data.common.annotation.EncryptTable;
import com.sync.data.common.statusmachine.StatusDefinition;
import com.sync.data.common.statusmachine.annotation.StatusMachine;
import com.sync.data.common.util.AESUtils;
import org.hibernate.EmptyInterceptor;
import org.hibernate.type.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.stream.IntStream;

/**
 * hibernate加解密拦截器
 */
@Component
public class CustomInterceptor extends EmptyInterceptor {

    @Value("${encrypt:false}")
    private boolean encrypt = false;

    private final static Logger LOGGER = LoggerFactory.getLogger(CustomInterceptor.class);

    /**
     * 更新时调用
     *
     * @param entity        实体类
     * @param id            主键
     * @param currentState  当前实体类对应的值
     * @param previousState 修改前实体类对应的值
     * @param propertyNames 字段名
     * @param types         实体类每个属性类型对应hibernate的类型
     * @return true | false true才会修改数据
     */
    @Override
    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {

        // 状态机校验
        verifyStatus(entity,currentState,previousState,propertyNames);

        Object[] newState = dealField(entity, currentState, propertyNames, "onFlushDirty");
        return super.onFlushDirty(entity, id, newState, previousState, propertyNames, types);
    }

    /**
     * 加载时调用
     *
     * @param entity        实体类
     * @param id            主键
     * @param state         实体类对应的值
     * @param propertyNames 字段名
     * @param types         实体类每个属性类型对应hibernate的类型
     * @return true | false true才会修改数据
     */
    @Override
    public boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
        Object[] newState = dealField(entity, state, propertyNames, "onLoad");
        return super.onLoad(entity, id, newState, propertyNames, types);
    }

    /**
     * 保存时调用
     *
     * @param entity        实体类
     * @param id            主键
     * @param state         实体类对应的值
     * @param propertyNames 字段名
     * @param types         实体类每个属性类型对应hibernate的类型
     * @return true | false true才会修改数据
     */
    @Override
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {

        // 状态机校验
        verifyStatus(entity,state,null,propertyNames);

        Object[] newState = dealField(entity, state, propertyNames, "onSave");
        return super.onSave(entity, id, newState, propertyNames, types);
    }


    public static void main(String[] args) {
        System.out.println(AESUtils.decryptStr("13"));
    }

    /**
     * 处理字段对应的数据
     *
     * @param entity        实体类
     * @param state         数据
     * @param propertyNames 字段名称
     * @return 解密后的字段名称
     */
    private Object[] dealField(Object entity, Object[] state, String[] propertyNames, String type) {

        try {
            List<String> annotationFields = getAnnotationField(entity);
            // 遍历字段名和加解密字段名
            for (String aField : annotationFields) {
                for (int i = 0; i < propertyNames.length; i++) {
                    if (!propertyNames[i].equals(aField)) {
                        continue;
                    }
                    // 如果字段名和加解密字段名对应且不为null或空
                    if (state[i] == null || Objects.equals(state[i].toString(), "")) {
                        continue;
                    }
                    if ("onSave".equals(type) || "onFlushDirty".equals(type)) {
                        LOGGER.info("当前字段：{}, 加密前：{}", aField, state[i]);
                        if ("idNum".equals(aField)||"idNumber".equals(aField)) {
                            LOGGER.info("保存时,对身份证类型的值自动大写");
                            state[i] = state[i].toString().toUpperCase();
                        }
                        try {
                            // 加密前先解密
                            LOGGER.info("加密前先解密");
                            String decryptStr = AESUtils.decryptStr(state[i].toString());
                            // 如果不报错无需加密直接保存
                            LOGGER.info("加密前先解密不报错：{}", decryptStr);
                            if (!encrypt){
                                state[i] = decryptStr;
                            }
                        }catch (Exception e){
                            // 如果报错，则需加密
                            if (encrypt) {
                                LOGGER.info("如果报错，则需加密");
                                state[i] = AESUtils.encryptHex(state[i].toString());
                            }
                        }
                        LOGGER.info("当前字段：{}, 加密后：{}", aField, state[i]);
                    } else if ("onLoad".equals(type)) {
                        LOGGER.info("当前字段：{}, 解密前：{}", aField, state[i]);
                        try {
                            state[i] = AESUtils.decryptStr(state[i].toString());}
                        catch (Exception e){
                            // 如果报错，则无所谓
                        }
                        LOGGER.info("当前字段：{}, 解密后：{}", aField, state[i]);
                    }
                }
            }
            return state;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取实体类中带有注解EncryptField的变量名
     *
     * @param entity 实体类
     * @return 需要加解密的字段
     */
    private List<String> getAnnotationField(Object entity) {
        // 判断当前实体类是否有加解密注解
        Class<?> entityClass = entity.getClass();
        if (!entityClass.isAnnotationPresent(EncryptTable.class)) {
            return Collections.emptyList();
        }
        List<String> fields = new ArrayList<>();
        // 获取实体类下的所有成员并判断是否存在加解密注解
        Field[] declaredFields = entityClass.getDeclaredFields();
        for (Field field : declaredFields) {
            EncryptField encryptField = field.getAnnotation(EncryptField.class);
            if (Objects.isNull(encryptField)) {
                continue;
            }
            fields.add(field.getName());
        }
        return fields;
    }

    /**
     * 状态根据状态机校验
     * @param entity 数据实体对象
     * @param currentState 当前数据
     * @param previousState 历史数据
     * @param propertyNames 属性列表
     * @return boolean
     */
    private boolean verifyStatus(Object entity,
                                 Object[] currentState,
                                 Object[] previousState,
                                 String[] propertyNames){
        // 获取对象的状态机注解
        StatusMachine statusMachine = entity.getClass().getAnnotation(StatusMachine.class);

        // 如果没有拦截器不做任何操作
        if (Objects.isNull(statusMachine)){
            return false;
        }

        // 获取status字段所在的index
        int[] idxStatus = IntStream.range(0, propertyNames.length)
                .filter(i -> "statusField".equals(propertyNames[i]))
                .toArray();

        int statusIdx = idxStatus[0];

        // 新状态
        String currentStatus = Objects.isNull(currentState)||Objects.isNull(currentState[statusIdx])?null:String.valueOf(currentState[statusIdx]);

        // 老状态
        String previousStatus = Objects.isNull(previousState)||Objects.isNull(previousState[statusIdx])?null:String.valueOf(previousState[statusIdx]);

        // 获取recordType
        int[] idxRecordType = IntStream.range(0, propertyNames.length)
                .filter(i -> "recordTypeField".equals(propertyNames[i]))
                .toArray();

        int recordTypeIdx = idxRecordType[0];

        // 根据当前数据自动进入是否有recordType的校验方法
        if(Objects.isNull(currentState[recordTypeIdx])){
            StatusDefinition.verifyStatus(currentStatus, previousStatus, ClassUtil.getClassName(entity,true));
        }else {
            String recordType = String.valueOf(currentState[recordTypeIdx]);
            if(StrUtil.isNotEmpty(recordType)){
                StatusDefinition.verifyStatus(currentStatus, previousStatus, ClassUtil.getClassName(entity,true), recordType);
            }else {
                StatusDefinition.verifyStatus(currentStatus, previousStatus, ClassUtil.getClassName(entity,true));
            }

        }
        return false;
    }

}

